/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.tools;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.*;

import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.tree.*;

/**
 * A checkbox tree selection model.
 * 
 * @author Doug Brown
 * 
 * Based on code by Santhosh Kumar T - santhosh@in.fiorano.com
 * See http://www.jroller.com/page/santhosh/20050610
 */
public class CheckTreeSelectionModel extends DefaultTreeSelectionModel { 
  private TreeModel model; 
  private PropertyChangeSupport support;
 
  /**
   * Constructor.
   *
   * @param model a TreeModel
   */
  public CheckTreeSelectionModel(TreeModel model) {
		this.model = model;
		setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
    support = new SwingPropertyChangeSupport(this);
	}

	/**
	 * Returns true if neither the path nor any descendant is selected.
	 * 
	 * @param path the path to test
	 */
	public boolean isPathUnselected(TreePath path) {
		if (isSelectionEmpty()) return true; // nothing is selected
		if (isPathOrAncestorSelected(path)) return false; // path is selected
		TreePath[] selectionPaths = getSelectionPaths();
		for (int i = 0; i < selectionPaths.length; i++) {
			if (path.isDescendant(selectionPaths[i])) // found a selected descendant
				return false;
		}
		return true;
	}

	/**
	 * Returns true if the path or any ancestor is selected.
	 * 
	 * @param path the path to test
	 */
	public boolean isPathOrAncestorSelected(TreePath path) {
		while (path != null && !isPathSelected(path))
			path = path.getParentPath();
		return path != null;
	}

	public void setSelectionPaths(TreePath[] paths) {
		super.clearSelection();
		addSelectionPaths(paths);
	}

	/**
	 * Adds paths to the current selection
	 * 
	 * @param paths the paths to add
	 */
	public void addSelectionPaths(TreePath[] paths) {
		if (paths == null) return;
		TreePath[] prev = getSelectionPaths();
		// unselect all descendants of paths
		for (int i = 0; i < paths.length; i++) {
			if (isSelectionEmpty()) break; // nothing to unselect
			TreePath path = paths[i];
			TreePath[] selectionPaths = getSelectionPaths();
			ArrayList toBeRemoved = new ArrayList();
			for (int j = 0; j < selectionPaths.length; j++) {
				if (path.isDescendant(selectionPaths[j])) 
					toBeRemoved.add(selectionPaths[j]);
			}
			super.removeSelectionPaths(
							(TreePath[])toBeRemoved.toArray(new TreePath[0]));
		}
		// if all siblings selected, unselect them and select parent recursively, 
		// otherwise select the path
		for (int i = 0; i < paths.length; i++) {
			TreePath path = paths[i];
			TreePath temp = null;
			while (isSiblingsSelected(path)) {
				temp = path;
				if (path.getParentPath() == null) break;
				path = path.getParentPath();
			}
			if (temp != null) {
				if (temp.getParentPath() != null) addSelectionPath(temp.getParentPath());
				else { // selected path is root, so unselect all others
					if (!isSelectionEmpty()) removeSelectionPaths(getSelectionPaths());
					super.addSelectionPaths(new TreePath[] {temp});
				}
			}
			else {
				super.addSelectionPaths(new TreePath[] {path});
			}
		}
    support.firePropertyChange("treepaths", prev, getSelectionPaths()); //$NON-NLS-1$
	}

	/**
	 * Removes paths from the current selection
	 * 
	 * @param paths the paths to remove
	 */
	public void removeSelectionPaths(TreePath[] paths) {
		if (isSelectionEmpty()) return; // nothing to remove
		TreePath[] prev = getSelectionPaths();
		for (int i = 0; i < paths.length; i++) {
			TreePath path = paths[i];
			if (path.getPathCount() == 1) { // root node
				super.removeSelectionPaths(new TreePath[] {path});
			}
			else {
				if (isPathSelected(path)) {
					super.removeSelectionPaths(new TreePath[] {path});
				}
				else unselectAncestor(path);
			}
		}
    support.firePropertyChange("treepaths", prev, getSelectionPaths()); //$NON-NLS-1$
	}

  /**
   * Adds a PropertyChangeListener.
   *
   * @param listener the object requesting property change notification
   */
  public void addPropertyChangeListener(PropertyChangeListener listener) {
    support.addPropertyChangeListener(listener);
  }

  /**
   * Removes a PropertyChangeListener.
   *
   * @param listener the listener requesting removal
   */
  public void removePropertyChangeListener(PropertyChangeListener listener) {
    support.removePropertyChangeListener(listener);
  }

	/**
	 * Determines whether all siblings of given path are selected
	 * 
	 * @param path the path to test
	 * @return true if all siblings selected
	 */
	private boolean isSiblingsSelected(TreePath path) {
		TreePath parent = path.getParentPath();
		if (parent == null) return true;
		Object node = path.getLastPathComponent();
		Object parentNode = parent.getLastPathComponent();
		int childCount = model.getChildCount(parentNode);
		for (int i = 0; i < childCount; i++) {
			Object childNode = model.getChild(parentNode, i);
			if (childNode.equals(node)) continue;
			if (!isPathSelected(parent.pathByAddingChild(childNode))) return false;
		}
		return true;
	}

	/**
	 * Unselects the ancestor of a path and selects ancestor's descendants
	 * other than those for which the path is a descendant.
	 * 
	 * @param path the path
	 */
	private void unselectAncestor(TreePath path) {
		// find selected ancestor 
		Stack stack = new Stack();
		stack.push(path);
		TreePath ancestor = path.getParentPath();
		while (ancestor != null && !isPathSelected(ancestor)) {
			stack.push(ancestor);
			ancestor = ancestor.getParentPath();
		}
		if (ancestor == null) { // no selected ancestors!
			return;
		}
		stack.push(ancestor);
		// for each path in the stack, unselect it and select all child nodes
		while (!stack.isEmpty()) {
			TreePath next = (TreePath)stack.pop();
			super.removeSelectionPaths(new TreePath[] {next});
			if (stack.isEmpty()) return;
			Object node = next.getLastPathComponent();
			int childCount = model.getChildCount(node);
			for (int i = 0; i < childCount; i++) {
				Object child = model.getChild(node, i);
				super.addSelectionPaths(new TreePath[] {next.pathByAddingChild(child)});
			}
		}
	}
}
/*
 * Open Source Physics software is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.
 * 
 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be
 * released under the GNU GPL license.
 * 
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston MA 02111-1307 USA or view the license online at
 * http://www.gnu.org/copyleft/gpl.html
 * 
 * Copyright (c) 2007 The Open Source Physics project
 * http://www.opensourcephysics.org
 */
